package com.zastoupil.pgc.blueprint_pattern.xml;

import java.io.File;
import java.io.IOException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;

/**
 * Responsible for providing JAXB utilities related to the {@link Blueprint},
 * {@link Modifier}, and {@link Factory} classes. Implements the singleton
 * pattern.
 * 
 * @see Blueprint
 * @see Modifier
 * @see Factory
 * 
 * @author Peter Zastoupil
 * 
 */
public class JAXBUtils {

	private static XMLConfig _config;

	private static JAXBContext _blueprintJAXBContext;
	private static Unmarshaller _blueprintUnmarshaller;
	private static Marshaller _blueprintMarshaller;

	private static JAXBContext _modifierJAXBContext;
	private static Unmarshaller _modifierUnmarshaller;
	private static Marshaller _modifierMarshaller;

	private static JAXBContext _factoryJAXBContext;
	private static Unmarshaller _factoryUnmarshaller;
	private static Marshaller _factoryMarshaller;

	private static JAXBUtils _instance;

	private JAXBUtils() {

	}

	private static void init() {

		// Initialize configuration
		_config = XMLConfigReader.getXMLConfig();

		// Initialize directory structure
		File directoryToCreate = new File(_config.getBlueprintDirectoryPath());

		if (!directoryToCreate.exists()) {

			try {
				directoryToCreate.createNewFile();
			} catch (IOException e) {
				System.err.println("Could not create Blueprint file directory");
				e.printStackTrace();
			}

		}

		directoryToCreate = new File(_config.getModifierDirectoryPath());

		if (!directoryToCreate.exists()) {

			try {
				directoryToCreate.createNewFile();
			} catch (IOException e) {
				System.err.println("Could not create Modifier file directory");
				e.printStackTrace();
			}

		}

		directoryToCreate = new File(_config.getFactoryDirectoryPath());

		if (!directoryToCreate.exists()) {

			try {
				directoryToCreate.createNewFile();
			} catch (IOException e) {
				System.err.println("Could not create Factory file directory");
				e.printStackTrace();
			}

		}

		// Initialize JAXB for Blueprint
		try {
			_blueprintJAXBContext = JAXBContext.newInstance(Blueprint.class);
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize JAXB context for the Blueprint class");
			e.printStackTrace();
		}
		try {
			_blueprintUnmarshaller = _blueprintJAXBContext.createUnmarshaller();
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize XML unmarshaller for the Blueprint class");
			e.printStackTrace();
		}
		try {
			_blueprintMarshaller = _blueprintJAXBContext.createMarshaller();
			_blueprintMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
					true);
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize XML marshaller for the Blueprint class");
			e.printStackTrace();
		}

		// Initialize JAXB for Modifier
		try {
			_modifierJAXBContext = JAXBContext.newInstance(Modifier.class);
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize JAXB context for the Modifier class");
			e.printStackTrace();
		}
		try {
			_modifierUnmarshaller = _modifierJAXBContext.createUnmarshaller();
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize XML unmarshaller for the Modifier class");
			e.printStackTrace();
		}
		try {
			_modifierMarshaller = _modifierJAXBContext.createMarshaller();
			_modifierMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
					true);
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize XML marshaller for the Modifier class");
			e.printStackTrace();
		}

		// Initialize JAXB for Factory
		try {
			_factoryJAXBContext = JAXBContext.newInstance(Factory.class);
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize JAXB context for the Blueprint class");
			e.printStackTrace();
		}
		try {
			_factoryUnmarshaller = _factoryJAXBContext.createUnmarshaller();
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize XML unmarshaller for the Blueprint class");
			e.printStackTrace();
		}
		try {
			_factoryMarshaller = _factoryJAXBContext.createMarshaller();
			_factoryMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT,
					true);
		} catch (JAXBException e) {
			System.err
					.println("Could not initialize XML marshaller for the Factory class");
			e.printStackTrace();
		}

		// Initialize instance
		_instance = new JAXBUtils();

	}

	/**
	 * Part of the implementation of the singleton design pattern, this method
	 * is the only entry point for external classes attempting to make use of
	 * the utility functions of JAXBUtils.
	 * 
	 * @return the singleton instance of the JAXBUtils class
	 */
	public static JAXBUtils getInstance() {

		if (_instance == null) {
			JAXBUtils.init();
		}

		return _instance;

	}

	/**
	 * Marshals a given Object to an XML file based on the XML configuration
	 * file
	 * 
	 * @param o
	 *            the object to be marshaled to an XML file
	 * @return <code>true</code> if the object was marshaled to an XML file
	 *         successfully, <code>false</code> otherwise
	 */
	public boolean marshal(Object o) {

		if (o instanceof Blueprint) {
			return this.marshal((Blueprint) o);
		} else if (o instanceof Modifier) {
			return this.marshal((Modifier) o);
		} else if (o instanceof Factory) {
			return this.marshal((Factory) o);
		} else {
			return false;
		}

	}

	private boolean marshal(Blueprint blueprint) {

		File blueprintFile = new File(_config.getBlueprintDirectoryPath()
				+ blueprint.getName() + _config.getBlueprintFileSuffix());

		try {
			_blueprintMarshaller.marshal(blueprint, blueprintFile);
			return true;
		} catch (JAXBException e) {
			System.err.println("Could not marshal Blueprint object to file");
			e.printStackTrace();
			return false;
		}

	}

	private boolean marshal(Modifier modifier) {

		File modifierFile = new File(_config.getModifierDirectoryPath()
				+ modifier.getName() + _config.getModifierFileSuffix());

		try {
			_modifierMarshaller.marshal(modifier, modifierFile);
			return true;
		} catch (JAXBException e) {
			System.err.println("Could not marshal Modifier object to file");
			e.printStackTrace();
			return false;
		}

	}

	private boolean marshal(Factory factory) {

		File factoryFile = new File(_config.getFactoryDirectoryPath()
				+ factory.getName() + _config.getFactoryFileSuffix());

		try {
			_factoryMarshaller.marshal(factory, factoryFile);
			return true;
		} catch (JAXBException e) {
			System.err.println("Could not marshal Factory object to file");
			e.printStackTrace();
			return false;
		}

	}

	/**
	 * Unmarshalls an Object from an XML file designated by the given file name.
	 * Note that the expected String is a file name, not a file path. The
	 * expected file name patterns are defined in the XML configuration file.
	 * 
	 * @param fileName
	 *            the file name of the XML file to be unmarshalled
	 * @return an Object unmarshalled from the XML file indicated by the given
	 *         file name, or <code>null</code> if no such file could be found.
	 */
	public Object unmarshal(String fileName) {

		Object result;

		if (fileName.endsWith(_config.getBlueprintFileSuffix())) {
			result = this.unmarshalBlueprint(fileName);
		} else if (fileName.endsWith(_config.getModifierFileSuffix())) {
			result = this.unmarshalModifier(fileName);
		} else if (fileName.endsWith(_config.getFactoryFileSuffix())) {
			result = this.unmarshalFactory(fileName);
		} else {
			result = null;
		}

		return result;

	}

	private Blueprint unmarshalBlueprint(String blueprintName) {

		Blueprint result = null;

		File fileToUnmarshal = new File(_config.getBlueprintDirectoryPath()
				+ blueprintName);

		if (fileToUnmarshal.exists()) {

			try {
				result = (Blueprint) _blueprintUnmarshaller
						.unmarshal(fileToUnmarshal);
			} catch (JAXBException e) {
				System.err
						.println("Could not unmarshal Blueprint object from file: "
								+ blueprintName);
				e.printStackTrace();
			}

		}

		return result;

	}

	private Modifier unmarshalModifier(String modifierName) {

		Modifier result = null;

		File fileToUnmarshal = new File(_config.getModifierDirectoryPath()
				+ modifierName);

		if (fileToUnmarshal.exists()) {

			try {
				result = (Modifier) _modifierUnmarshaller
						.unmarshal(fileToUnmarshal);
			} catch (JAXBException e) {
				System.err
						.println("Could not unmarshal Modifier object from file: "
								+ modifierName);
				e.printStackTrace();
			}

		}

		return result;

	}

	private Factory unmarshalFactory(String factoryName) {

		Factory result = null;

		File fileToUnmarshal = new File(_config.getFactoryDirectoryPath()
				+ factoryName);

		if (fileToUnmarshal.exists()) {

			try {
				result = (Factory) _factoryUnmarshaller
						.unmarshal(fileToUnmarshal);
			} catch (JAXBException e) {
				System.err
						.println("Could not unmarshal Factory object from file: "
								+ factoryName);
				e.printStackTrace();
			}

		}

		return result;

	}

}
